﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mimoza.Transport;

namespace Mimoza.AgentLayer.Communication
{
    public class ProcessorHostRegistration : BaseObject
    {
        public ProcessorHostRegistration(Configuration config) : base(config)
        {
            m_hostRegistration = new HostRegistration(config);
        }

        override public void Init()
        {
            RegisterInTransport();
            
            HostRegistration.Init();

            ReStart();
        }

        override public void Shutdown()
        {
            StopFindServer();
            CloseConnection();
        }

        public void ReStart()
        {
            Shutdown();
            CreateUDPListener();
            StartFindServer();
        }

        public void RegisterInTransport()
        {
            RequestBroadcastServerAddress.RegisterHandler(
                new RequestBroadcastServerAddress.Handler(ProcessRequestServerAddress));
            RequestSetServerAddress.RegisterHandler(
                new RequestSetServerAddress.Handler(ProcessRequestSetServerAddress));

            RequestRegisterNewHost.RegisterInTransport();
            ReplyRegisterNewHost.RegisterInTransport();
        }

        void CreateUDPListener()
        {
            m_hostUDPConnection = Mimoza.Transport.Transport.CreateServerUDPConnection(
                Configuration.Settings.HostUDPTransportPort);
            Mimoza.Transport.Transport.Dispatcher.AddConnection(m_hostUDPConnection);
        }

        void CreateBroadcaster()
        {
            if (Configuration.Settings.BroadcastTransportPort == 0)
            {
                Common.Logger.Log.Warn("ProcessorHostRegistration.CreateBroadcaster broadcasting disabled.");
                return;
            }

            try
            {
                if (!string.IsNullOrEmpty(Configuration.Settings.ServerAddr))
                {
                    // create UDP connection to concreate service host (not broadcast)
                    m_broadcaster = Mimoza.Transport.Transport.CreateUDPConnection(
                        Configuration.Settings.ServerAddr, Configuration.Settings.BroadcastTransportPort);
                }
                else
                {
                    m_broadcaster = Mimoza.Transport.Transport.CreateUDPBroadcast(
                        Configuration.Settings.BroadcastTransportPort);
                }
            }
            catch (Exception e)
            {
                Common.Logger.Log.Error("ProcessorHostRegistration.CreateBroadcaster error - {0}.", e.ToString());
            }
        }

        void CloseConnection()
        {
            if (m_broadcaster != null)
            {
                m_broadcaster.Close();
                m_broadcaster = null;
            }

            if (m_hostUDPConnection != null)
            {
                Mimoza.Transport.Transport.Dispatcher.RemoveConnection(
                    m_hostUDPConnection);
                m_hostUDPConnection = null;
            }
        }

        void StartFindServer()
        {
            if (HostRegistration.IsHostRegistered()) return;

            HostRegistration.GenerateHostId();
            CreateBroadcaster();

            StartFindServerThread();
        }

        void StopFindServer()
        {
            Common.Logger.Log.Info("ProcessorHostRegistration.StopFindServer count - " +
                (m_requestCountNum + 1).ToString() + ".");

            StopFindServerThread();
        }

        private void StartFindServerThread()
        {
            if (m_broadcaster == null)
            {
                Common.Logger.Log.Warn("ProcessorHostRegistration.StartFindServerThread error. Broadcaster is not initialized.");
                return;
            }

            m_findServerThread = new System.Threading.Thread(new
                System.Threading.ThreadStart(FindServerThread));

            m_stopEvent = new System.Threading.AutoResetEvent(false);

            m_findServerThread.Start();
        }

        private void StopFindServerThread()
        {
            if (m_findServerThread == null) return;

            m_stopEvent.Set();
            m_findServerThread.Join();
            m_findServerThread = null;
        }

        void FindServerThread()
        {
            Common.Logger.Log.Info("ProcessorHostRegistration.FindServerThread...");

            while (true)
            {
                SendFindServerSignal();
                bool bWaitRes = m_stopEvent.WaitOne(Configuration.Settings.FindServerPeriod,
                    false);
                if (bWaitRes == true) break;
            }
 
            Common.Logger.Log.Info("ProcessorHostRegistration.FindServerThread...OK");
        }


        void SendFindServerSignal()
        {
            Common.Logger.Log.Info("ProcessorHostRegistration.SendFindServerSignal count - " +
                (m_requestCountNum + 1).ToString() + "...");

            m_broadcaster.SendRequest(
                new RequestBroadcastServerAddress(HostRegistration.HostId, LocalInfo.HostName,
                    m_requestCountNum++, Configuration.Settings.HostUDPTransportPort));

            Common.Logger.Log.Info("ProcessorHostRegistration.SendFindServerSignal...OK");
        }
        
        /// <summary>
        /// This procedure accept request from othe agent and reply to other agent 
        /// address of server.
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>        
        TransferObject ProcessRequestServerAddress(RequestBroadcastServerAddress request)
        {
            if (request.CountNumber == 0 ||
                (request.CountNumber % Configuration.Settings.AcceptedFindServerRequestNumber) != 0)
            {
                return null;
            }

            if (!HostRegistration.IsHostRegistered())
            {
                Common.Logger.Log.Info("ProcessorHostRegistration can't accept request from other hosts because this host is not registered yet.");
                return null;
            }

            Common.Logger.Log.Info("ProcessorHostRegistration accept request from other hosts: id - '"
                + request.HostId + "' remoteAddr - '"
                + request.RemoteAddress + "'.");

            // send serverAddr to host
            Connection reverseUdpConn = Mimoza.Transport.Transport.CreateUDPConnection(
                request.RemoteAddress, Configuration.Settings.BroadcastTransportPort);

            RequestSetServerAddress sendRequest = new RequestSetServerAddress(request.HostId, 
                HostRegistration.ServerHost, "");
            sendRequest.IsFromServer = false;
            reverseUdpConn.SendRequest(sendRequest);

            reverseUdpConn.Close();

            return null;
        }

        TransferObject ProcessRequestSetServerAddress(RequestSetServerAddress request)
        {
            string serverIpAddr = request.IsFromServer ? request.RemoteAddress : request.ServerHostIpAddr;

            Common.Logger.Log.Info("ProcessorHostRegistration StartHostRegistration name - '"
                + request.ServerHostName + "' addr - '" + serverIpAddr + "'.");

            if (serverIpAddr == null) return null;

            MakeHostRegistration(serverIpAddr, request.ServerHostName);
            
            return null;
        }

        public void MakeHostRegistration(string serverIpAddr, string serverHostName)
        {
            Connection serverConn = Mimoza.Transport.Transport.CreateConnection(serverIpAddr, Configuration.Settings.ServerTransportPort);

            Communication.ReplyRegisterNewHost reply = SendHostInfoWithReply(serverConn);
            if (reply != null)
            {
                HostRegistration.SaveServerInfo(serverHostName, serverIpAddr,
                    reply.OverrrideHostId);
                StopFindServer();
            }
            else
            {
                Common.Logger.Log.Error("ProcessorHostRegistration.MakeHostRegistration error. reply is empty or HostId is wrong.");

            }


            serverConn.Close();
        }

        public void SendHostInfo(Connection serverConn)
        {
            SendHostInfoWithReply(serverConn);
        }

        Communication.ReplyRegisterNewHost SendHostInfoWithReply(Connection serverConn)
        {
            FillHostInfo();

            Communication.RequestRegisterNewHost requestToServer = new
                Communication.RequestRegisterNewHost(HostRegistration.HostId);
            requestToServer.HostName = m_hostName;
            requestToServer.MacAddresses = Common.ArrayToString.ToString(m_macAddresses);
            requestToServer.IPAddresses = Common.ArrayToString.ToString(m_ipAddresses);
            requestToServer.OSName = m_OS;
            requestToServer.ModelName = m_ModelName;
            requestToServer.ModelVersion = m_ModelVersion;


            TransferObject replyObj;
            serverConn.MakeRequest(requestToServer, out replyObj);
            Communication.ReplyRegisterNewHost reply = replyObj as Communication.ReplyRegisterNewHost;

            if (reply == null && HostRegistration.CheckHostId(reply.ToHostId))
            {
                Common.Logger.Log.Error("ProcessorHostRegistration.SendHostInfo error. Host id in not equal.");
            }

            return reply;
        }

        HostRegistration HostRegistration
        {
            get { return m_hostRegistration; }
        }

        void FillHostInfo()
        {
            lock (m_hostName)
            {
                if (m_hostName != null && m_hostName != "") return;

                AdaptersList adapters = LocalInfo.GetAdapters();
                m_hostName = LocalInfo.HostName;                
                m_macAddresses = adapters.GetMACAddresses();
                m_ipAddresses = adapters.GetIPAddresses();

                m_OS = Common.GlobalSettings.Licence.OSName;
                m_ModelName = Common.GlobalSettings.Licence.ModelName;
                m_ModelVersion = Common.GlobalSettings.Licence.Version;

                Common.Logger.Log.Info("ProcessorHostRegistration.FillHostInfo hostName - {0} os - {1} model - {2} version - {3} ip - {4} mac - {5}.",
                    m_hostName, m_OS, m_ModelName, m_ModelVersion, 
                    m_ipAddresses[0].ToString(), m_macAddresses[0].ToString() );
            }
        }

        HostRegistration m_hostRegistration;

        Connection m_broadcaster;
        Connection m_hostUDPConnection;

        string m_hostName = "";
        string[] m_macAddresses;
        string[] m_ipAddresses;
        string m_OS;
        string m_ModelName;
        string m_ModelVersion;

        System.Threading.Thread m_findServerThread;
        System.Threading.AutoResetEvent m_stopEvent;
        int m_requestCountNum = 0; // counter of findserver request
    }
}
